﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class DConnRecord : BiffRecord
    {
        private DConnParameter[] _params;
        private ushort ai;
        private byte bVerDbqueryEdit;
        private byte bVerDbqueryRefreshableMin;
        private byte bVerDbqueryRefreshed;
        private object connection;
        private byte credMethod;
        private DataSourceType dbt;
        private bool fDeleted;
        private bool fSavePwd;
        private bool fStandAlone;
        private bool fTableNames;
        private bool fTablesOnlyHtml;
        private FrtHeaderOld frtHeaderOld;
        private ConnGrbitDbt grbitDbt;
        private DConnId id;
        private byte jp;
        private uint rcc;
        private ushort reserved;
        private DConnStringSequence rgbEditWebPage;
        private DConnStringSequence rgbSQL;
        private DConnStringSequence rgbSQLSav;
        private DConnUnicodeStringSegmented rgchConnectionDesc;
        private DConnUnicodeStringSegmented rgchConnectionName;
        private DConnUnicodeStringSegmented rgchSSOApplicationID;
        private DConnUnicodeStringSegmented rgchSourceConnectionFile;
        private DConnUnicodeStringSegmented rgchSourceDataFile;
        private DConnUnicodeStringSegmented tableNames;
        private ushort wHtmlFmt;
        private ushort wRefreshInterval;
        private bool fAlwaysUseConnectionFile;
        private bool fBackgroundQuery;
        private bool fRefreshOnLoad;
        private bool fSaveData;

        internal override BiffRecordType RecordType
        {
            get { return BiffRecordType.DConn; }
        }

        protected override void ReadContent(BiffReaderContext context, int length)
        {
#if DEBUG
            long pos = context.Reader.BaseStream.Position;
#endif
            frtHeaderOld = new FrtHeaderOld(context.Reader);
            Contract.Assert(frtHeaderOld.rt == 0x0876);
            dbt = (DataSourceType) context.ReadUInt16();
            ai = context.ReadUInt16();
            fSavePwd = (ai & 0x1) != 0;
            fTablesOnlyHtml = (ai & 0x2) != 0;
            fTableNames = (ai & 0x4) != 0;
            fDeleted = (ai & 0x8) != 0;
            fStandAlone = (ai & 0x10) != 0;
            this.fAlwaysUseConnectionFile = (ai & 0x20) != 0;
            this.fBackgroundQuery = (ai & 0x40) != 0;
            fRefreshOnLoad = (ai & 0x80) != 0;
            this.fSaveData = (ai & 0x100) != 0;
            ushort cParams = context.ReadUInt16();
            reserved = context.ReadUInt16();
            jp = context.ReadByte();
            context.ReadByte();
            grbitDbt = (ConnGrbitDbt) context.ReadUInt16();
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif

            bVerDbqueryEdit = context.ReadByte();
            bVerDbqueryRefreshed = context.ReadByte();
            bVerDbqueryRefreshableMin = context.ReadByte();
            wRefreshInterval = context.ReadUInt16();
            wHtmlFmt = context.ReadUInt16();
            rcc = context.ReadUInt32();
            credMethod = context.ReadByte();
            byte reserved3 = context.ReadByte();
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif

            rgchSourceDataFile = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            rgchSourceConnectionFile = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            rgchConnectionName = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            rgchConnectionDesc = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            rgchSSOApplicationID = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            if (fTableNames)
                tableNames = new DConnUnicodeStringSegmented(context.Reader);
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            if (fStandAlone)
            {
                _params = new DConnParameter[cParams];
                for (int i = 0; i < cParams; i++)
                {
                    var p = new DConnParameter(context.Reader);
                    _params[i] = p;
                }
            }
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            switch (dbt)
            {
                case (DataSourceType) 1:
                    var a = new DConnUnicodeStringSegmented(context.Reader);
                    connection = a;
                    break;
                case (DataSourceType) 4:
                    var b = new DConnConnectionWeb();
                    b.Read(context.Reader);
                    connection = b;
                    break;
                case (DataSourceType) 5:
                    var c = new DConnConnectionOleDb();
                    c.Read(context.Reader);
                    connection = c;
                    break;
                case (DataSourceType) 6:
                    var d = new TxtQry();
                    d.Read(context.Reader);
                    connection = d;
                    break;
                default:
                    break;
            }
#if DEBUG
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
            rgbSQL = new DConnStringSequence(context.Reader);
            rgbSQLSav = new DConnStringSequence(context.Reader);
            rgbEditWebPage = new DConnStringSequence(context.Reader);
            id = new DConnId();
            id.Read(context.Reader);
#if DEBUG
            long pos2 = context.Reader.BaseStream.Position;
            Contract.Assert((pos2 - pos) == length);
            Contract.Assert((context.Reader.BaseStream.Position - pos) <= length);
#endif
        }

        internal override void WriteContent(BiffWriterWorkbookContext context)
        {
            throw new NotImplementedException();
        }
    }

    internal class TxtQry
    {
        private ushort ae;
        private ushort chCustom;
        private byte chDecimal;
        private byte chThousSep;
        private byte fl;
        private int itwf;
        private XLUnicodeString rgchFile;
        private List<TxtWf> rgtxtwf;
        private int rowStartAt;
        private ushort rt;
        private ushort unused1;
        private byte unused2;

        public void Read(BinaryReader reader)
        {
            rt = reader.ReadUInt16();
            ushort reserved = reader.ReadUInt16();
            ae = reader.ReadUInt16();
            unused1 = reader.ReadUInt16();
            rowStartAt = reader.ReadInt32();
            fl = reader.ReadByte();
            chCustom = reader.ReadUInt16();
            unused2 = reader.ReadByte();
            itwf = reader.ReadInt32();
            chDecimal = reader.ReadByte();
            chThousSep = reader.ReadByte();
            rgtxtwf = new List<TxtWf>();
            for (int i = 0; i < itwf; i++)
            {
                var TxtWf = new TxtWf();
                TxtWf.Read(reader);
                rgtxtwf.Add(TxtWf);
            }
            rgchFile = new XLUnicodeString();
            rgchFile.Read(reader);
        }
    }

    internal class TxtWf
    {
        private uint fieldStart;
        private uint fieldType;

        public void Read(BinaryReader reader)
        {
            fieldType = reader.ReadUInt32();
            fieldStart = reader.ReadUInt32();
        }
    }

    internal class DConnConnectionOleDb
    {
        private ushort cOleDb;
        private uint nDrillthroughRows;
        private List<DConnUnicodeStringSegmented> rgConn;
        private byte[] rgIOleDbInvalid;
        private List<ushort> rgIOleDbValid;
        private ushort unused;

        public void Read(BinaryReader reader)
        {
            nDrillthroughRows = reader.ReadUInt32();
            cOleDb = reader.ReadUInt16();
            rgIOleDbValid = new List<ushort>();
            for (int i = 0; i < cOleDb; i++)
                rgIOleDbValid.Add(reader.ReadUInt16());
            rgIOleDbInvalid = reader.ReadBytes(2*(4 - cOleDb))
                ;
            unused = reader.ReadUInt16();
            rgConn = new List<DConnUnicodeStringSegmented>();
            for (int i = 0; i < cOleDb; i++)
            {
                rgConn.Add(new DConnUnicodeStringSegmented(reader));
            }
        }
    }

    internal class DConnConnectionWeb
    {
        private DConnStringSequence rgbURL;
        private DConnStringSequence rgbWebPost;

        public void Read(BinaryReader reader)
        {
            rgbURL = new DConnStringSequence(reader);
            rgbWebPost = new DConnStringSequence(reader);
        }
    }

    internal class DConnId
    {
        private byte bType;
        private object data;

        public void Read(BinaryReader reader)
        {
            bType = reader.ReadByte();
            switch (bType)
            {
                case 0:
                    break;
                case 1:
                    var a = new DConnUnicodeStringSegmented(reader);
                    data = a;
                    break;
                case 2:
                    var b = new SXStreamID();
                    b.Read(reader);
                    data = b;
                    break;
                default:
                    throw new NotSupportedException();
            }
        }
    }

    internal class SXStreamID
    {
        private ushort idStm;

        public void Read(BinaryReader reader)
        {
            idStm = reader.ReadUInt16();
        }
    }

    internal class DConnStringSequence
    {
        private readonly List<DConnUnicodeStringSegmented> rgString;

        public DConnStringSequence(BinaryReader context)
        {
            ushort cst = context.ReadUInt16();
            rgString = new List<DConnUnicodeStringSegmented>();
            for (int i = 0; i < cst; i++)
            {
                var s = new DConnUnicodeStringSegmented(context);
                rgString.Add(s);
            }
        }
    }

    internal enum ODBCType : ushort
    {
        SQL_TYPE_NULL = 0x0000,
//Undetermined type, data source (1) does not support typed data. Data type determined based on data content: date and time, decimal or text.

        SQL_CHAR = 0x0001,
//Fixed-length string of ANSI characters

        SQL_DECIMAL = 0x0003,
//Fixed-precision, Fixed-scale numbers

        SQL_INTEGER = 0x0004,
//32-bit signed integer

        SQL_SMALLINT = 0x0005,
//16-bit signed integer

        SQL_FLOAT = 0x0006,
//User-specified precision floating-point

        SQL_REAL = 0x0007,
//7-digits precision floating-point

        SQL_DOUBLE = 0x0008,
//15-digits precision floating-point

        SQL_TIMESTAMP = 0x000B,
//Date and Time

        SQL_VARCHAR = 0x000C,
//Variable-length string of ANSI characters

        SQL_BIT = 0xFFF9,
//Bit (1 or 0)

        SQL_BINARY = 0xFFFE,
//Fixed-length binary data
    }

    internal class DConnParameter
    {
        private readonly DConnParamBinding paramBinding;
        private readonly ushort pbtreserved;
        private ushort aunused;
        private DConnUnicodeStringSegmented rgchName;
        private ODBCType wTypeSql;

        public DConnParameter(BinaryReader reader)
        {
            rgchName = new DConnUnicodeStringSegmented(reader);
            pbtreserved = reader.ReadUInt16();
            bool pbt = (pbtreserved & 0x1) != 0;
            wTypeSql = (ODBCType) reader.ReadUInt16();
            aunused = reader.ReadUInt16();

            paramBinding = new DConnParamBinding();
            paramBinding.Read(reader, pbt);
        }
    }

    internal class DConnParamBinding
    {
        private object _value;

        public void Read(BinaryReader reader, bool pbt)
        {
            if (!pbt)
            {
                _value = new DConnUnicodeStringSegmented(reader);
            }
            else
            {
                _value = new DConnParamBindingValType(reader);
            }
        }
    }

    internal class DConnParamBindingValType
    {
        private readonly ushort wBindingValueGrbit;
        private object _value;

        public DConnParamBindingValType(BinaryReader reader)
        {
            wBindingValueGrbit = reader.ReadUInt16();
            switch (wBindingValueGrbit)
            {
                case 0x01:
                    _value = reader.ReadDouble();
                    break;
                case 0x02:
                    _value = new DConnParamBindingValString(reader);
                    break;
                case 0x04:
                    _value = new DConnParamBindingValByte(reader);
                    break;
                case 0x0800:
                    _value = new DConnParamBindingValInt(reader);
                    break;
                default:
                    throw new NotSupportedException();
            }
            throw new NotImplementedException();
        }
    }

    internal class DConnParamBindingValInt
    {
        private uint val;

        public DConnParamBindingValInt(BinaryReader reader)
        {
            val = reader.ReadUInt32();
            uint reserved = reader.ReadUInt32();
        }
    }

    internal class DConnParamBindingValByte
    {
        private byte fVal;

        public DConnParamBindingValByte(BinaryReader reader)
        {
            fVal = reader.ReadByte();
            reader.ReadBytes(7);
        }
    }

    internal class DConnParamBindingValString
    {
        private DConnUnicodeStringSegmented rgchBindingValueStr;

        public DConnParamBindingValString(BinaryReader reader)
        {
            reader.ReadBytes(8);
            rgchBindingValueStr = new DConnUnicodeStringSegmented(reader);
        }
    }

    internal class DConnUnicodeStringSegmented
    {
        private readonly XLUnicodeStringSegmented st;

        public DConnUnicodeStringSegmented(BinaryReader context)
        {
            st = new XLUnicodeStringSegmented();
            st.Read(context);
        }
    }

    internal class XLUnicodeStringSegmented
    {
        private List<XLUnicodeString> strings;

        public void Read(BinaryReader reader)
        {
            uint cchTotal = reader.ReadUInt32();
            strings = new List<XLUnicodeString>();
            int totalSoFar = 0;
            while (totalSoFar < cchTotal)
            {
                XLUnicodeString s = new XLUnicodeString();
                s.Read(reader);
                this.strings.Add(s);
                totalSoFar += s.Cch;
            }
/*            for (int i = 0; i < cchTotal; i++)
            {
                var s = new XLUnicodeString();
                s.Read(reader);
                strings.Add(s);
            }
  */
        }
    }

    internal enum ConnGrbitDbt : ushort
    {
        ConnGrbitDbtWeb = 4,
        ConnGrbitDbtOledb = 5,
        ConnGrbitDbtAdo = 6,
    }

    internal struct FrtHeaderOld
    {
        private FrtFlags grbitFrt;
        internal ushort rt;

        public FrtHeaderOld(BinaryReader reader)
        {
            rt = reader.ReadUInt16();
            grbitFrt = (FrtFlags) reader.ReadUInt16();
        }
    }
}